#pragma once

#include <glibmm.h>
#include <assert.h>

namespace BuildNG
{
  template <class BaseType>
  class TypesRegistry
  {
  public:
    typedef Glib::RefPtr<BaseType> (*CreateFunc)();

    class TypeInfo : public Object
    {
    public:
      TypeInfo();

      void                    set_xml_tag_name(const Glib::ustring &name) { xml_tag_name = name; }
      const Glib::ustring&    get_xml_tag_name() const { return xml_tag_name; };

      void                    set_create_func(CreateFunc create_func) { this->create_func = create_func; }
      template <class CreateThisType>
      void                    set_create_func_from_ctor();

      Glib::RefPtr<BaseType>  create_instance();

    private:
      Glib::ustring           xml_tag_name;
      CreateFunc              create_func;
    };

    void                      register_type(Glib::RefPtr<TypeInfo> type_info);

    Glib::RefPtr<TypeInfo>    get_by_xml_tag_name(const Glib::ustring &tag_name);

  private:
    std::map<Glib::ustring,Glib::RefPtr<TypeInfo> > types_by_xml_tag_name;
    typedef typename std::map<Glib::ustring,Glib::RefPtr<TypeInfo> >::iterator ByTagNameIt;
    typedef typename std::map<Glib::ustring,Glib::RefPtr<TypeInfo> >::const_iterator ConstByTagNameIt;

    template <class T>
    static Glib::RefPtr<BaseType> ctor_create_func()
    {
      return Glib::RefPtr<BaseType>(new T);
    }
  };

  //////////
  // TypesRegistry<BaseType> implementation
  template <class BaseType>
  void TypesRegistry<BaseType>::register_type(Glib::RefPtr<TypeInfo> type_info)
  {
    assert(types_by_xml_tag_name.find(type_info->get_xml_tag_name()) == types_by_xml_tag_name.end() &&
      "There's already a type registered under this name.");
    types_by_xml_tag_name.insert(std::make_pair(type_info->get_xml_tag_name(),type_info));
  }

  template <class BaseType>
  Glib::RefPtr<typename TypesRegistry<BaseType>::TypeInfo> TypesRegistry<BaseType>::get_by_xml_tag_name(const Glib::ustring &tag_name)
  {
    ByTagNameIt it = types_by_xml_tag_name.find(tag_name);
    if(it != types_by_xml_tag_name.end())
      return it->second;
    else
      return Glib::RefPtr<TypeInfo>();   // return a null ptr
  }

  //////////
  // TypesRegistry<BaseType>::TypeInfo implementation
  template <class BaseType>
  TypesRegistry<BaseType>::TypeInfo::TypeInfo()
  {
    create_func = 0;
  }

  template <class BaseType>
  template <class CreateThisType>
  void TypesRegistry<BaseType>::TypeInfo::set_create_func_from_ctor()
  {
    create_func = ctor_create_func<CreateThisType>;
  }

  template <class BaseType>
  Glib::RefPtr<BaseType> TypesRegistry<BaseType>::TypeInfo::create_instance()
  {
    assert(create_func);
    return create_func();
  }
}
